#include "Program.h"

namespace Dicewars
{
	Program::Program()
	{
		framesPainted = 0;

		isButtonDown = false;

		rotateDown = rotateUp = false;
		rotateLeft = rotateRight = false;
		zoomIn = zoomOut = false;

		camera = new Camera(Vector3(-10,8,-10),		//eye
							Vector3(-2,5,-2),		//look
							Vector3(0.0, 1.0, 0.0), //up
							1.0f, 90.0f, 
							glutGet(GLUT_WINDOW_WIDTH) / (float) glutGet(GLUT_WINDOW_HEIGHT) );

		glClear(GL_COLOR_BUFFER_BIT |GL_DEPTH_BUFFER_BIT);
		glShadeModel(GL_SMOOTH);
		glEnable(GL_DEPTH_TEST);
		glDepthFunc(GL_LEQUAL);
		glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	}

	Program::~Program()
	{
		delete scene;
		delete status;
		delete lightZero;
		delete lightOne;
		delete camera;
		delete game;
		delete timer;
		delete playerZero;
		delete playerOne;
		delete playerTwo;
		delete playerThree;
	}

	void Program::ResizeWindow(int w, int h)
	{
		camera->ResizeWindow(w, h);
	}

	void Program::HandleKeyPresses(unsigned char key, int x, int y)
	{
		switch(key)
		{
			case ESC:	exit(0);
			break;

			// Change to next player
			case SPACE: game->CancelRound ();
			break;

			// Rotate clockwise around y-axis
			case 'a':	// fall through
			case 'A':	rotateLeft = true;
			break;

			// Rotate clockwise around x-axis
			case 'w':	// fall through
			case 'W':	rotateUp = true;
			break;

			// Rotate counter-clockwise around x-axis
			case 's':	// fall through
			case 'S':	rotateDown = true;
			break;

			// Rotate counter-clockwise around y-axis
			case 'd':	// fall through
			case 'D':	rotateRight = true;
			break;

			// Set zoom in
			case '+':	zoomIn = true;
			break;

			// Set zoom out
			case '-':	zoomOut = true;
			break;

			// Disable/Enable Shaders
			case 'l':	// fall through
			case 'L':	shaders = !shaders;
			break;

			// Disable/Enable FPS
			case 't':	// fall through
			case 'T':	showFPS = !showFPS;
			break;	

			// Reset camera
			case 'r':	// fall through
			case 'R':	camera->Reset();
			break;
			
			default:	break;
		}
	}

	void Program::HandleSpecialKeyPresses(int key, int x, int y)
	{
		switch(key)
		{		
			// Toggle Framerate
			case GLUT_KEY_F2:	ConfigSingleton::Instance()->SetShowFPS(!ConfigSingleton::Instance()->GetShowFPS());
			break;

			// Toggle Wireframe
			case GLUT_KEY_F3:	ConfigSingleton::Instance()->SetWireframe(!ConfigSingleton::Instance()->GetWireframe());
			break;

			// Toggle DisplayLists
			case GLUT_KEY_F7:
			{
				switch(ConfigSingleton::Instance()->GetRenderMode())
				{
					case RenderImmediate:
						{
							ConfigSingleton::Instance()->SetRenderMode(RenderDisplayList);
							break;
						}
					case RenderDisplayList:
						{
							ConfigSingleton::Instance()->SetRenderMode(RenderImmediate);
							break;
						}
				}
				break;
			}

			// Toggle Culling
			case GLUT_KEY_F8:	ConfigSingleton::Instance()->SetFrustumCulling(!ConfigSingleton::Instance()->GetFrustumCulling());
			break;
			
			default:	break;
		}
	}

	void Program::HandleKeyReleases (unsigned char key, int x, int y)
	{
		switch (key)
		{
			// Rotate clockwise around y-axis
			case 'a':	// fall through
			case 'A':	rotateLeft = false;
			break;

			// Rotate clockwise around x-axis
			case 'w':	// fall through
			case 'W':	rotateUp = false;
			break;

			// Rotate counter-clockwise around x-axis
			case 's':	// fall through
			case 'S':	rotateDown = false;
			break;

			// Rotate counter-clockwise around y-axis
			case 'd':	// fall through
			case 'D':	rotateRight = false;
			break;

			// Set zoom in
			case '+':	zoomIn = false;
			break;

			// Set zoom out
			case '-':	zoomOut = false;
			break;
		}
	}

	void Program::HandleMouseClicks(int button, int state, int x, int y)
	{
		mouseButton = button;

		switch(state)
		{
			case GLUT_DOWN:
			{
				lastMouseX = x;
				lastMouseY = y;
				isButtonDown = true;
				break;
			}

			case GLUT_UP:
			{
				isButtonDown = false;
				break;
			}

			default: 
				break;
		}

		if(button == GLUT_LEFT_BUTTON && isButtonDown == true) 
		{
			BoardTile* previousTile = board->GetSelectedTile();
			BoardTile* currentTile = NULL;
			SelectableObject* currentObj = scene->GetObjectUnderMouse(pickRay);
			currentTile = (BoardTile*)currentObj;
			if(currentTile != NULL)
			{
				if(game->GetActivePlayer()->GetPlayerType() == HumanPlayer)
				{
					game->PerformTurn(previousTile, currentTile);
				}
			}
		}
	}

	void Program::HandlePassiveMouseMotion(int x, int y)
	{
		lastMouseX = x;
		lastMouseY = y;
		if (currentTile != NULL)
		{
			previousTile = currentTile;
		}
		currentTile = NULL;
		SelectableObject* currentObj = scene->GetObjectUnderMouse(pickRay);
		currentTile = (BoardTile*)currentObj;
		if(currentTile)
		{
			GLfloat tilePositionX = currentTile->GetModel()->GetTranslation()->x;
			GLfloat tilePositionY = currentTile->GetModel()->GetTranslation()->y;
			GLfloat tilePositionZ = currentTile->GetModel()->GetTranslation()->z;
			lightOne->SetPosition(tilePositionX, lightOne->GetPosition()[1], tilePositionZ, 1.0);
			lightOne->SetDirection(0.0, -1.0, 0.0);

		}
		board->SetHoveredTile(currentTile);
	}

	void Program::HandleActiveMouseMotion(int x, int y)
	{
		//if (!isButtonDown) return;
		float weight = 0.05f;
		switch (mouseButton)
		{
			case GLUT_RIGHT_BUTTON:
			{
				float dx = camera->TransformMouseX() * x - camera->TransformMouseX() * lastMouseX;
				float dy = camera->TransformMouseY() * y - camera->TransformMouseY() * lastMouseY;
				const float w = 1;
				camera->RotateHorizontal (w*dx);
				camera->RotateVertical   (w*dy);
				break;
			}

			default:
			{
				break;
			}
		}

		lastMouseX = x;
		lastMouseY = y;
	}

	void Program::Render()
	{
		timer->Start();
		scene->Draw();
		pickRay->Update(lastMouseX, lastMouseY, glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT));
		glutSwapBuffers();
		framesPainted++;
		secondsPassed =	timer->Stop();
		GLenum err = glGetError();
		while (err != GL_NO_ERROR)
		{
			printf("%s caught at %s:%u\n", (char *)gluErrorString(err), __FILE__, __LINE__);
			err = glGetError();
		}
		if(ConfigSingleton::Instance()->GetShowFPS())
		{
			status->SetFPS(1000/secondsPassed);
		}
	}

	void Program::UpdateGameState()
	{
		float weight = 0.075f;
		if (rotateLeft == true)
		{
			camera->MoveHorizontal(0.1f * secondsPassed);
		}
		if (rotateUp == true)
		{
			camera->MoveVertical(0.1f * secondsPassed);
		}
		if (rotateDown == true)
		{
			camera->MoveVertical(-0.1f * secondsPassed);
		}
		if (rotateRight == true)
		{
			camera->MoveHorizontal(-0.1f * secondsPassed);
		}
		if (zoomIn == true)
		{
			camera->Zoom(0.1 * secondsPassed * weight);
		}
		if (zoomOut == true)
		{
			camera->Zoom(-0.1 * secondsPassed * weight);
		}
		game->Update(secondsPassed);
		glutPostRedisplay();
	}

	void Program::Init()
	{
		lastMouseX = 0;
		lastMouseY = 0;
		scene = new Scene(camera);
		lightZero = new PointLightSource();
		lightZero->SetPosition(12.0, 15.0, 12.0, 0.0);
		lightZero->SetAmbient(0.3, 0.3, 0.3, 0.4);
		lightZero->SetDiffuse(0.6, 0.6, 0.6, 1.0);
		lightZero->SetSpecular(0.1, 0.1, 0.2, 0.5);
		lightOne = new SpotLightSource();
		lightOne->SetPosition(0.0, 6.0, 0.0, 1.0);
		lightOne->SetAmbient(0.0, 0.0, 0.0, 0.0);
		lightOne->SetDiffuse(1.0, 1.0, 1.0, 1.0);
		lightOne->SetSpecular(1.0, 1.0, 1.0, 1.0);
		lightOne->SetExponent(36.0);
		lightOne->SetCutoff(64.0);
		pickRay = new PickRay();
		pickRay->Update(lastMouseX, lastMouseY, glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT));
		scene->GetObjectUnderMouse(pickRay);
		board = new Board();
		status = new StatusBar();
		timer = new Timer();
		scene->AddHUDElement(status);
		scene->AddLight(lightZero);
		scene->AddLight(lightOne);
		playerZero = new Player(0, ComputerPlayer, 1.0,  0.0, 0.0);
		playerOne = new Player(1, ComputerPlayer, 0.0,  1.0, 0.0);
		playerTwo = new Player(2, HumanPlayer, 0.0,  0.0, 1.0);
		playerThree = new Player(3, ComputerPlayer, 1.0,  1.0, 0.0);
		game = new Game(scene);
		game->AddBoard(board);
		game->AddStatusBar(status);
		game->Join(playerZero);
		game->Join(playerOne);
		game->Join(playerTwo);	
		game->Join(playerThree);	
		Duck* decoratorDuck = new Duck();
		decoratorDuck->GetModel()->SetTranslation(24.0f, -1.8f, 23.4f);
		decoratorDuck->GetModel()->SetRotation(46.0f, 0.0f, 1.0f, 0.0f);
		decoratorDuck->GetModel()->SetScale(0.02f, 0.02f, 0.02f);
		scene->AddObject(decoratorDuck);
		board->GetModel()->SetScale(16.8f, 3.0f, 16.8f);
		board->GetModel()->SetTranslation(10.0f, -2.0f, 9.0);
		scene->AddObject(board);
		game->Start();
	}
}